
#include <libusb.h>
#include <QDir>
#include <QFileDialog>
#include <QMessageBox>
#include <QScrollBar>

#include "mainwindow.h"
#include "memchunkdialog.h"
#include "rangeselectdialog.h"
#include "ui_mainwindow.h"


MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ph( new PhoneHandler( this ) ),
    mdThread( new MemDumpThread( ph, this ) ),
    pollingTimer( new QTimer( this ) ),
    progressBar( new QProgressBar( this ) ),
    statusRight( new QLabel( this ) ),
    browseDir( QDir::currentPath() ),
    chunkViewMode( MemChunkDialog::V_AbsoluteOffset | MemChunkDialog::V_Ascii ),
    ui( new Ui::MainWindow )
{
    ui->setupUi( this );

    // log window
    ui->plainTextEdit_log->setReadOnly( true );
    QFont monoFont;
    monoFont = QFont( "Courier New", QApplication::font().pointSize() );
    ui->plainTextEdit_log->setFont( monoFont );
    QPalette p = ui->plainTextEdit_log->palette();
    p.setColor( QPalette::Base, QColor( "#ffffff" ) );
    p.setColor( QPalette::Text, QColor( "#000000" ) );
    ui->plainTextEdit_log->setPalette( p );

    // stuff in the lower right corner
    ui->statusBar->addPermanentWidget( progressBar, 0 );
    ui->statusBar->addPermanentWidget( statusRight, 0 );
    progressBar->setVisible( false );

    // DL mode panel
    const QFontMetrics &fm( fontMetrics() );
    ui->treeWidget_memRegions->header()->resizeSection( COL_Desc, fm.width( QString( 19 , 'W' ) ) );
    ui->treeWidget_memRegions->header()->resizeSection( COL_Addr, fm.width( QString( 9 , 'W' ) ) );
    ui->treeWidget_memRegions->header()->resizeSection( COL_Size, fm.width( QString( 9 , 'W' ) ) );
    ui->treeWidget_memRegions->header()->resizeSection( COL_FileName, fm.width( QString( 15 , 'W' ) ) );
    ui->treeWidget_memRegions->header()->resizeSection( COL_Type, fm.width( QString( 10 , 'W' ) ) );


    SetupGui( PhoneHandler::NoDevice );

    int r = libusb_init( NULL );
    if( r < 0 )
    {
        QMessageBox::warning( this, "Libusb Error", QString( "Failed to initialize libusb (%1)" ).arg( r ) );
        return;
    }

    connect( pollingTimer, SIGNAL(timeout()), this, SLOT(PollingTimerTimeOut()) );
    ui->actionDetect_Phone->setChecked( true );

    connect( mdThread, SIGNAL(SendProgress(int)), progressBar, SLOT(setValue(int)) );
    connect( mdThread, SIGNAL(SendDone(quint32,quint32,QString)), this, SLOT(ThreadDone(quint32,quint32,QString)) );
    connect( mdThread, SIGNAL(SendFatalError(QString)), this, SLOT(ThreadFatalError(QString)) );
}

MainWindow::~MainWindow()
{
    delete mdThread;
    delete ph;
    delete ui;
    libusb_exit( NULL );
}

QPlainTextEdit *MainWindow::LogWindow()
{
    return ui ? ui->plainTextEdit_log : NULL;
}

void MainWindow::SetupGui( PhoneHandler::DeviceMode m )
{
    ui->treeWidget_memRegions->clear();
    switch( m )
    {
    case PhoneHandler::NoDevice:
    {
        statusRight->setText( "No Phone" );
        ui->stackedWidget->setCurrentIndex( 0 );
    }
    break;
    case PhoneHandler::DL_Mode:
    {
        statusRight->setText( "Download Mode" );
        ui->stackedWidget->setCurrentIndex( 2 );
    }
    break;
    case PhoneHandler::Diag_Mode:
    {
        statusRight->setText( "Diag Mode" );
        ui->stackedWidget->setCurrentIndex( 1 );

        ui->lineEdit_meid->clear();
        ui->lineEdit_MSL->clear();
        ui->lineEdit_ESN->clear();
    }
    break;
    }
}

void MainWindow::PollingTimerTimeOut()
{
    switch( ph->Mode() )
    {
    case PhoneHandler::NoDevice:
    {
        switch( ph->FindDevice() )
        {
        default:
            break;
        case PhoneHandler::Diag_Mode:
        {
            // just detected a new phone in Diag Mode
            //ph->GetEsn();

            //ph->BaseBandVersion();

            SetupGui( PhoneHandler::Diag_Mode );

            // read some useful NV items
            u64 meid;
            if( ph->ReadNV_MEID( meid ) )
            {
                ui->lineEdit_meid->setText( QString( "%1" ).arg( meid, 0, 16 ) );
            }
            if( ph->ReadNV_BlueTooth( meid ) )
            {
                QString bluetooth = QString( "%1" ).arg( meid, 0, 16 );
                // 123456789a
                for( int i = 10; i > 1; i-=2 )
                {
                    bluetooth.insert( i, ':' );
                }
                ui->lineEdit_bt->setText( bluetooth );
            }
            u32 esn;
            if( ph->ReadNV_ESN( esn ) )
            {
                ui->lineEdit_ESN->setText( QString( "%1" ).arg( esn, 8, 16, QChar( '0') ) );
            }
            if( ph->NVIdentify() )
            {
                ui->lineEdit_MSL->setText( ph->ReadNV_MSL() );
            }
            ui->lineEdit_unlockKey->setText( ph->ReadNV_UnlockCode() );
            ui->lineEdit_otsl->setText( ph->ReadNV_OTSL() );
            ui->lineEdit_mdn->setText( ph->ReadNV_MDN() );

            QByteArray ba = ph->GetLCD();
            if( ba.size() )
            {
                QImage im( (uchar*)ba.constData(), 320, 480, QImage::Format_RGB16 );
                ui->label_LCD->setPixmap( QPixmap::fromImage( im ) );
            }

        }
        break;
        case PhoneHandler::DL_Mode:
        {
            // just detected a new phone in DL Mode
            SetupGui( PhoneHandler::DL_Mode );
            QList< MemRegion > regions;
            if( ph->GetMemoryRegions( regions ) )
            {
                qDebug() << "got" << regions.size() << "memory regions";
                foreach( const MemRegion &r, regions )
                {
                    QStringList txt = QStringList()
                            << r.desc
                            << QString( "%1" ).arg( r.address, 8, 16, QChar( '0' ) )
                            << QString( "%1" ).arg( r.size, 8, 16, QChar( '0' ) )
                            << r.fileName
                            << ( r.type == MemRegion::Recommended ? "Recommended" : r.type == MemRegion::Mandatory ? "Mandatory" : "Other" );
                    new QTreeWidgetItem( ui->treeWidget_memRegions, txt );
                }
            }
            //ph->BaseBandVersion();
            //ph->GetEsn();
        }
        break;
        }
    }
    break;
    case PhoneHandler::DL_Mode:
    {
        if( !ph->PollDownloadMode() )
        {
            ph->Close();
            DBG << "Closing phone handler because polling DL mode failed";

            // phone has been unplugged or something like that.
            // disable gui elements and stuff here
            SetupGui( PhoneHandler::NoDevice );
        }
    }
    break;
    case PhoneHandler::Diag_Mode:
    {
        if( !ph->PollDiagMode() )
        {
            ph->Close();
            DBG << "Closing phone handler because polling Diag mode failed";

            // phone has been unplugged or something like that.
            // disable gui elements and stuff here
            SetupGui( PhoneHandler::NoDevice );
        }
    }
    break;
    }
}

void MainWindow::on_actionDetect_Phone_toggled( bool arg1 )
{
    if( arg1 )
    {
        pollingTimer->start( PollInterval );
    }
    else
    {
        pollingTimer->stop();
    }
}

void MainWindow::on_actionDump_Region_triggered()
{
    QList< QTreeWidgetItem * >its = ui->treeWidget_memRegions->selectedItems();
    if( !its.size() )
    {
        DBG << "!size";
        return;
    }

    QTreeWidgetItem *sel = its.at( 0 );
    QString fName = QFileDialog::getSaveFileName( this, tr( "Save File" ), browseDir + '/' + sel->text( COL_FileName ) );
    if( fName.isEmpty() )
    {
        return;
    }
    browseDir = QFileInfo( fName ).absolutePath();

    u32 addr = sel->text( COL_Addr ).toUInt( NULL, 16 );
    u32 size = sel->text( COL_Size ).toUInt( NULL, 16 );

    //qDebug() << ::hex << addr << size << fName;

    // stop some stuff from messing with the phone while we dump the ram
    pollingTimer->stop();
    ui->actionDetect_Phone->setEnabled( false );

    // show the progress bar
    progressBar->setVisible( true );

    // start doing stuff
    if( !mdThread->DumpRegion( addr, size, fName ) )
    {
        WRN << "Error starting dump thread";
        SetGuiForThreadDone();
        return;
    }

    dumpStart.restart();

    ui->pushButton_dumpRegion->setText( tr( "Cancel Dump" ) );
    ui->pushButton_dumpChunk->setEnabled( false );
    ui->pushButton_reset->setEnabled( false );
}

void MainWindow::on_treeWidget_memRegions_currentItemChanged( QTreeWidgetItem *current, QTreeWidgetItem *previous )
{
    Q_UNUSED( previous );
    ui->actionDump_Region->setEnabled( ( current != NULL ) );
    ui->pushButton_dumpRegion->setEnabled( ( current != NULL ) );
}

void MainWindow::ThreadDone( quint32 address, quint32 size, const QString &path )
{
    QString unit;
    float val = size;
    if( val > 1024.f )
    {
        val /= 1024.f;
        if( val > 1024.f )
        {
            val /= 1024.f;
            unit = "MiB";
        }
        else
        {
            unit = "KiB";
        }
    }
    else
    {
        unit = "bytes";
    }

    QTime e( 0, 0, 0 );
    e = e.addMSecs( dumpStart.elapsed() );

    QString m = QString( tr( "Dumped 0x%1 bytes (%2 %3) from 0x%6 in %4 to %5" )
                         .arg( size, 8, 16, QChar( '0' ) )
                         .arg( val, 0, 'f', 2 )
                         .arg( unit )
                         .arg( e.toString( "m:ss.zzz") )
                         .arg( path ) )
                         .arg( address, 8, 16, QChar( '0' ) );
    qDebug() << qPrintable( m );
    SetGuiForThreadDone();
}

void MainWindow::ThreadFatalError( const QString &e )
{
    WRN << e;
    SetGuiForThreadDone();
}

void MainWindow::SetGuiForThreadDone()
{
    if( ui->actionDetect_Phone->isChecked() )
    {
        pollingTimer->start( PollInterval );
    }
    ui->actionDetect_Phone->setEnabled( true );
    progressBar->setVisible( false );

    ui->pushButton_dumpRegion->setText( tr( "Dump Region" ) );
    ui->pushButton_dumpChunk->setEnabled( true );
    ui->pushButton_reset->setEnabled( true );
}

void MainWindow::OutputMessage( QtMsgType type, const QString &msg )
{
    bool needToScroll = ui->plainTextEdit_log->verticalScrollBar()->value() == ui->plainTextEdit_log->verticalScrollBar()->maximum();//if the text window is already showing the last line
    switch( type )
    {
    case QtDebugMsg:
        ui->plainTextEdit_log->appendHtml( QString( msg ) );
        break;
    case QtWarningMsg:
        {
            QString htmlString = "<b><text style=\"color:#0000ff\">" + msg + "</text></b>";
            ui->plainTextEdit_log->appendHtml( htmlString );
        }
        break;
    case QtCriticalMsg:
        {
            QString htmlString = "<b><text style=\"color:#ff0000\">" + msg + "</text></b>";
            ui->plainTextEdit_log->appendHtml( htmlString );
        }
        break;
    case QtFatalMsg:
        fprintf(stderr, "Fatal: %s\n", msg.toLatin1().data() );
        abort();
        break;
    }
    if( needToScroll )//scroll to the new bottom of the screen
    {
        ui->plainTextEdit_log->verticalScrollBar()->setValue( ui->plainTextEdit_log->verticalScrollBar()->maximum() );
    }
}

void MainWindow::on_pushButton_dumpRegion_clicked()
{
    if( mdThread->isRunning() )
    {
        mdThread->CancelDump();
    }
    else
    {
        on_actionDump_Region_triggered();
    }
}

void MainWindow::on_pushButton_reset_clicked()
{
    if( QMessageBox::question( this, tr( "Reboot?" ), tr( "Do you want to reboot the phone?"),
                               QMessageBox::Yes | QMessageBox::No, QMessageBox::No ) == QMessageBox::Yes )
    {
        qDebug() << "Sending reboot command...";
        if( !ph->RebootPhone() )
        {
            // maybe show an error or something here?
        }
    }
}

void MainWindow::on_pushButton_BootDlMode_clicked()
{
    if( QMessageBox::question( this, tr( "Reboot?" ), tr( "Do you want to reboot the phone into download mode?"),
                               QMessageBox::Yes | QMessageBox::No, QMessageBox::No ) == QMessageBox::Yes )
    {
        qDebug() << "Sending reboot command...";
        if( !ph->RebootToDownloadMode() )
        {
            // maybe show an error or something here?
        }
    }
}

void MainWindow::on_pushButton_dumpChunk_clicked()
{
    u32 addr = 0x6D88E90 - 0x30;// my msl is usually at the start of this chunk, but rolling it backwards 0x30 bytes just in case somebody else's isn't
    u32 len = 0xff0;

    /*QList< QTreeWidgetItem * >its = ui->treeWidget_memRegions->selectedItems();
    if( its.size() )
    {
        QTreeWidgetItem *sel = its.at( 0 );
        addr = sel->text( COL_Addr ).toUInt( NULL, 16 );
        len = sel->text( COL_Size ).toUInt( NULL, 16 );
    }*/

    if( !RangeSelectDialog::GetRange( addr, len, chunkViewMode, this ) )
    {
        return;
    }

    // keep somebody from trying to view a 256MiB file
    if( len > PhoneHandler::MaxMemChunkSize )
    {
        WRN << "length > 0xff0.  truncating it";
        len = PhoneHandler::MaxMemChunkSize;
    }

    // make sure the values are in range
    bool ok = false;
    for( int i = 0; i < ui->treeWidget_memRegions->topLevelItemCount(); i++ )
    {
        QTreeWidgetItem *it =  ui->treeWidget_memRegions->topLevelItem( i );
        u32 a = it->text( COL_Addr ).toUInt( NULL, 16 );
        u32 l = it->text( COL_Size ).toUInt( NULL, 16 );

        if( addr >= a && ( a + l ) <= ( addr + len ) )
        {
            ok = true;
            break;
        }
    }
    if( !ok )
    {
        WRN << "Didn\'t find any memory range in the list that contains the range: addr" << addr << "len:" << len;
        return;
    }

    QByteArray ba = ph->DumpMemory( addr, len );
    if( !ba.size() )
    {
        // error
        qDebug() << "Read Error occurred.  Not showing the dialog";
        return;
    }

    MemChunkDialog::ShowArray( ba, addr, chunkViewMode, this );
}

void MainWindow::on_actionClear_Log_triggered()
{
    ui->plainTextEdit_log->clear();
}

void MainWindow::on_pushButton_nvRead_clicked()
{
    const QString &t = ui->lineEdit_nvid->text();
    if( !t.size() )
    {
        return;
    }
    ui->plainTextEdit_nvHex->clear();
    bool ok = false;
    quint16 n = t.toUInt( &ok, 0 );
    if( !ok )
    {
        qWarning() << "Error converting" << t << "to a number";
    }
    qDebug() << "Reading NV item" << qPrintable( QString( "%1 [0x%2]" ).arg( n ).arg( n, 0, 16 ) );
    QByteArray ba = ph->ReadNVItem( n );

    ui->plainTextEdit_nvHex->appendPlainText( MemChunkDialog::MakeHexDump( ba, 0 ) );
}
